﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;
using EffiProz.Core.Results;

namespace EffiProz.Core
{
    public class StatementProcedure : StatementDMQL
    {
        /** Expression to evaluate */
        public Expression expression;

        /** Routine to execute */
        public Routine procedure;

        /** arguments to Routine */
        public Expression[] arguments;
        public ResultMetaData resultMetaData;

        /**
         * Constructor for CALL statements for expressions.
         */
        public StatementProcedure(Session session, Expression expression,
                           ParserDML.CompileContext compileContext)
            : base(StatementTypes.CALL, StatementTypes.X_SQL_DATA,
                session.getCurrentSchemaHsqlName())
        {

            this.expression = expression;

            setDatabseObjects(compileContext);
            checkAccessRights(session);
        }

        /**
         * Constructor for CALL statements for procedures.
         */
        public StatementProcedure(Session session, Routine procedure,
                           Expression[] arguments, ParserDML.CompileContext compileContext) :
            base(StatementTypes.CALL, StatementTypes.X_SQL_DATA,
                  session.getCurrentSchemaHsqlName())
        {



            this.procedure = procedure;
            this.arguments = arguments;

            setDatabseObjects(compileContext);
            checkAccessRights(session);
        }

        public override Result getResult(Session session)
        {
            return expression == null ? getProcedureResult(session)
                                      : getExpressionResult(session);
        }

        public Result getProcedureResult(Session session)
        {

            Object[] data = new object[0];

            if (arguments.Length > 0)
            {
                data = new Object[arguments.Length];
            }

            for (int i = 0; i < arguments.Length; i++)
            {
                Expression e = arguments[i];

                if (e != null)
                {
                    data[i] = e.getValue(session, e.dataType);
                }
            }

            int variableCount = procedure.getVariableCount();

            session.sessionContext.push();

            session.sessionContext.routineArguments = data;
            session.sessionContext.routineVariables = new object[0];

            if (variableCount > 0)
            {
                session.sessionContext.routineVariables =
                    new Object[variableCount];
            }

            // fixed? temp until assignment of dynamicArguments in materialiseSubqueries is fixed
            //        Object[] args   = session.sessionContext.dynamicArguments;
            Result result = procedure.statement.execute(session);

            //        session.sessionContext.dynamicArguments = args;
            if (!result.isError())
            {
                result = Result.updateZeroResult;
            }

            Object[] callArguments = session.sessionContext.routineArguments;

            session.sessionContext.pop();

            if (result.isError())
            {
                return result;
            }

            bool returnParams = false;

            for (int i = 0; i < procedure.getParameterCount(); i++)
            {
                ColumnSchema param = procedure.getParameter(i);
                int mode = param.getParameterMode();

                if (mode != SchemaObjectParameterModes.PARAM_IN)
                {
                    if (this.arguments[i].isParam())
                    {
                        int paramIndex = arguments[i].parameterIndex;

                        session.sessionContext.dynamicArguments[paramIndex] =
                            callArguments[i];
                        returnParams = true;
                    }
                    else
                    {
                        int varIndex = arguments[i].getColumnIndex();

                        session.sessionContext.routineVariables[varIndex] =
                            callArguments[i];
                    }
                }
            }

            if (returnParams)
            {
                result = Result.newCallResponse(
                    this.getParametersMetaData().getParameterTypes(), this.id,
                    session.sessionContext.dynamicArguments);
            }

            return result;
        }

        public Result getExpressionResult(Session session)
        {

            Object o;    // expression return value
            Result r;

            session.sessionData.startRowProcessing();

            o = expression.getValue(session);

            if (o is Result)
            {
                return (Result)o;
            }

            if (resultMetaData == null)
            {
                getResultMetaData();
            }

            /**
             * @todo 1.9.0 For table functions implment handling of Result objects
             * returned from Java functions. Review and document instantiation and usage
             * of relevant implementation of Result and JDBCResultSet for returning
             * from Java functions?
             * else if (o is JDBCResultSet) {
             *   return ((JDBCResultSet) o).getResult();
             * }
             */
            r = Result.newSingleColumnResult(resultMetaData);

            Object[] row = new Object[1];

            row[0] = o;

            r.getNavigator().add(row);

            return r;
        }

        public override ResultMetaData getResultMetaData()
        {

            if (resultMetaData != null)
            {
                return resultMetaData;
            }

            switch (type)
            {

                case StatementTypes.CALL:
                    {
                        if (expression == null)
                        {
                            return ResultMetaData.emptyResultMetaData;
                        }

                        // TODO:
                        //
                        // 1.) standard to register metadata for columns of
                        // the primary result set, if any, generated by call
                        //
                        // 2.) Represent the return value, if any (which is
                        // not, in truth, a result set), as an OUT parameter
                        //
                        // For now, I've reverted a bunch of code I had in place
                        // and instead simply reflect things as the are, describing
                        // a single column result set that communicates
                        // the return value.  If the expression generating the
                        // return value has a void return type, a result set
                        // is described whose single column is of type NULL
                        ResultMetaData md = ResultMetaData.newResultMetaData(1);
                        ColumnBase column =
                            new ColumnBase(null, null, null,
                                           StatementDMQL.RETURN_COLUMN_NAME);

                        column.setType(expression.getDataType());

                        md.columns[0] = column;

                        md.prepareData();

                        resultMetaData = md;

                        return md;
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "StatementProcedure");
            }
        }

        /**
         * Returns the metadata for the placeholder parameters.
         */
        public override ResultMetaData getParametersMetaData()
        {

            /** @todo - change the auto-names to the names of params */
            return base.getParametersMetaData();
        }

       public override void collectTableNamesForRead(OrderedHashSet set) { }
    }
}
